using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Web;

namespace Limestone
{
    public class PageStubCache : IPageStubCache
    {
        #region props and ctx

        public IStorage Storage { get; set; }
        public bool IsLoaded { get; private set; }

        public PageStubCache(IStorage storage)
        {
            Storage = storage;
   //         if (IsLoaded) return;
   //
   //         // todo lock or check loading state or similar
   //         LoadPageLookUpCache();
   //         IsLoaded = true;
        }

        private Dictionary<string, IEnumerable<IPageStub>> ByParentPath
        {
            get
            {
                var byParentPath = (Dictionary<string, IEnumerable<IPageStub>>)HttpRuntime.Cache["lime_allItemsByParentPath"];
                if (byParentPath == null)
                {
                    LoadPageLookUpCache(false);
                    byParentPath = (Dictionary<string, IEnumerable<IPageStub>>)HttpRuntime.Cache["lime_allItemsByParentPath"];
                }
                return byParentPath;
            }
            set
            {
                HttpRuntime.Cache["lime_allItemsByParentPath"] = value;
            }
        }
        private Dictionary<string, IPageStub> Pages
        {
            get
            {
                var pageStubs = (Dictionary<string, IPageStub>)HttpRuntime.Cache["lime_allItems"];
                if (pageStubs == null)
                {
                    LoadPageLookUpCache(false);
                    pageStubs = (Dictionary<string, IPageStub>)HttpRuntime.Cache["lime_allItems"];
                }
                return pageStubs;
            }
            set
            {

                HttpRuntime.Cache["lime_allItems"] = value;
            }
        }

        #endregion

        /// <summary>
        /// Caches all parent-child relations
        /// </summary>
        /// <param name="forceDeepReload"> </param>
        public void LoadPageLookUpCache(bool forceDeepReload)
        {
            var lookUpCache = Storage.GetPageLookUp(forceDeepReload);

            Pages = lookUpCache.Pages;
            ByParentPath = lookUpCache.PagesByParentId;
        }

        public void AddPageStub(IPageStub stub, string parentPath)
        {
            parentPath = parentPath.ToLower();

            EnsureParentPathExists(parentPath);

            ByParentPath[parentPath] = ByParentPath[parentPath].Concat(new[] { stub });

            if (!Pages.ContainsKey(stub.Path.ToLower()))
                Pages.Add(stub.Path, stub);

            Storage.AddPageToLookUp(stub);
        }

        private void EnsureParentPathExists(string parentPath)
        {
            if (ByParentPath.ContainsKey(parentPath))
                return;

            var grandParentPath = PageHelper.GetParentPath(parentPath);
            if (grandParentPath != "/")
                EnsureParentPathExists(grandParentPath);

            ByParentPath.Add(parentPath, Enumerable.Empty<IPageStub>());
        }

        /// <summary>
        /// Returns list of pagestubs that are direct descendants of path
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public IEnumerable<IPageStub> GetChildPageStubsFor(string path)
        {
            IEnumerable<IPageStub> children;
            ByParentPath.TryGetValue(path, out children);
            return children ?? Enumerable.Empty<IPageStub>();
        }
        public IEnumerable<IPageStub> GetRootPageStubs(bool excludeHidden)
        {
            return GetChildPageStubsFor("/");
        }

        public bool IsCached(string path)
        {
            return Pages.ContainsKey(path);
        }

        /// <summary>
        /// A quicker look up function
        /// </summary>
        /// <param name="path"></param>
        public IPageStub GetPageStub(string path)
        {
            IPageStub stub;
            Pages.TryGetValue(path.ToLower(), out stub);
            return stub;
        }


        public void RemovePageStub(string pagePath)
        {
            pagePath = pagePath.ToLower();
            var parentPath = PageHelper.GetParentPath(pagePath).ToLower();

            // get the page and its children to be removed 
            var childPaths = GetAllChildPaths(pagePath).Concat(new[] { pagePath });

            // do not operate directly on the caches when we have several operations to perform
            // TODO clone or lock
            var pagesTemp = Pages;
            var byParentPaths = ByParentPath;

            // remove the paths from the cache
            foreach (var childPath in childPaths)
            {
                pagesTemp.Remove(childPath);
                if (byParentPaths.ContainsKey(childPath))
                    byParentPaths.Remove(childPath);
            }

            // remove the page itself from its parent child listing
            byParentPaths[parentPath] = byParentPaths[parentPath].Where(p => p.Path != pagePath);

            // update caches
            Pages = pagesTemp;
            ByParentPath = byParentPaths;

            Storage.RemovePageFromLookUp(pagePath);
        }

        // Gather all paths in a single dimensional list
        private IEnumerable<string> GetAllChildPaths(string path)
        {
            IEnumerable<string> grandchildren = new string[] { };
            IEnumerable<IPageStub> stubs;
            ByParentPath.TryGetValue(path, out stubs);

            if (stubs == null)
                return Enumerable.Empty<string>();

            var children = stubs.Select(p => p.Path);
            foreach (var childPath in children)
            {
                grandchildren = children.Concat(GetAllChildPaths(childPath));
            }
            return children.Concat(grandchildren);
        }


        public void MovePageStub(string oldPagePath, string newParentPath)
        {
            var stub = GetPageStub(oldPagePath);
            RemovePageStub(stub.Path);

            stub.Path = newParentPath.Last() == '/' ? newParentPath + stub.Name : newParentPath + "/" + stub.Name;
            AddPageStub(stub, newParentPath);
        }

        public ILimeMapNode GetSiteMap(bool includeHidden)
        {
            return new LimeMapNode
                       {
                           Nodes = GetMapNodes(new []{GetPageStub("/")})
                       };

        }

        private IEnumerable<LimeMapNode> GetMapNodes(IEnumerable<IPageStub> nodes)
        {
            /*
            var res = new LimeMapNode[] {};

            foreach (var node in nodes)
            {
                var children = GetChildPageStubsFor(node.Path);
                var child = new LimeMapNode
                    {
                        PageStub = node,
                        Nodes = GetMapNodes(children)
                    };
                res = res.Concat(new[] {child}).ToArray();
            }
            return res;
            */
                 return nodes.Select(pageStub => new LimeMapNode
                                                {
                                                    PageStub = pageStub,
                                                    Nodes = GetMapNodes(GetChildPageStubsFor(pageStub.Path))
                                                });
        
        }
    }
}